/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the 'License');
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { ArkFile, ts, AstTreeUtils } from "arkanalyzer/lib";
import { BaseChecker, BaseMetaData } from "../BaseChecker";
import Logger, { LOG_MODULE_TYPE } from "arkanalyzer/lib/utils/logger";
import { RuleListUtil } from "../../utils/common/DefectsList";
import { Defects, IssueReport } from "../../model/Defects";
import { Rule } from "../../model/Rule";
import { FileMatcher, MatcherCallback, MatcherTypes } from "../../Index";
import { RuleFix } from "../../model/Fix";

const logger = Logger.getLogger(
  LOG_MODULE_TYPE.HOMECHECK,
  "CommaDangleCheck"
);

interface CommaDangleOptions {
  arrays?: string;
  objects?: string;
  imports?: string;
  exports?: string;
  functions?: string;
  enums?: string;
  generics?: string;
  tuples?: string;
}

export class CommaDangleCheck implements BaseChecker {
  readonly metaData: BaseMetaData = {
    severity: 2,
    ruleDocPath: "docs/comma-dangle-check.md",
    description: "Require or disallow trailing commas",
  };

  public issues: IssueReport[] = [];
  public defects: Defects[] = [];
  public rule: Rule;

  private fileMatcher: FileMatcher = {
    matcherType: MatcherTypes.FILE,
  };

  public registerMatchers(): MatcherCallback[] {
    const matchFileCb: MatcherCallback = {
      matcher: this.fileMatcher,
      callback: this.check,
    };
    return [matchFileCb];
  }

  private getRuleOption(type: keyof CommaDangleOptions): string {
    const option = this.rule.option?.[0];
    if (typeof option === 'string') {
      return option;
    }
    return typeof option === 'object' ? (option as CommaDangleOptions)[type] || 'never' : 'never';
  }

  public check = (target: ArkFile): void => {
    const code = target.getCode();
    const sourceFile = AstTreeUtils.getASTNode(target.getFilePath(), code);

    const visit = (node: ts.Node) => {
      if (ts.isArrayLiteralExpression(node)) {
        this.checkTrailingComma(node, target, 'arrays');
      } else if (ts.isObjectLiteralExpression(node)) {
        this.checkTrailingComma(node, target, 'objects');
      }
      
      if (ts.isEnumDeclaration(node)) {
        const ruleOption = this.getRuleOption('enums');
        this.checkEnumTrailingComma(node, target, ruleOption);
      }

      if (ts.isFunctionDeclaration(node) && node.typeParameters) {
        const ruleOption = this.getRuleOption('generics');
        if (ruleOption !== 'ignore') {
          this.checkGenericTrailingComma(node.typeParameters, target, ruleOption);
        }
      }

      if (ts.isTupleTypeNode(node)) {
        const ruleOption = this.getRuleOption('tuples');
        this.checkTupleTrailingComma(node, target, ruleOption);
      }

      if (ts.isImportDeclaration(node)) {
        const ruleOption = this.getRuleOption('imports');
        this.checkImportExportTrailingComma(node, target, ruleOption);
      }

      if (ts.isExportDeclaration(node)) {
        const ruleOption = this.getRuleOption('exports');
        this.checkImportExportTrailingComma(node, target, ruleOption);
      }

      if (ts.isCallExpression(node) || ts.isFunctionDeclaration(node) || ts.isNewExpression(node)) {
        const ruleOption = this.getRuleOption('functions');
        this.checkFunctionTrailingComma(node, target, ruleOption);
      }

      ts.forEachChild(node, visit);
    };

    ts.forEachChild(sourceFile, visit);
    
    this.issues.sort((a, b) => {
      if (a.defect.reportLine !== b.defect.reportLine) {
        return a.defect.reportLine - b.defect.reportLine;
      }
      return a.defect.reportColumn - b.defect.reportColumn;
    });
  };

  private findTrailingCommaPos(lastElement: ts.Node, node: ts.Node): { pos: number, column: number } {
    const sourceText = lastElement.getSourceFile().getFullText();
    let commaPos = -1;
    let column = -1;
    
    for (let i = lastElement.getEnd(); i < node.getEnd(); i++) {
      if (sourceText[i] === ',') {
        let foundClosing = false;
        for (let j = i + 1; j < node.getEnd(); j++) {
          if (!/[\s\r\n]/.test(sourceText[j])) {
            if (sourceText[j] === '}' || sourceText[j] === ']' || sourceText[j] === '>') {
              foundClosing = true;
            }
            break;
          }
        }
        if (foundClosing) {
          commaPos = i;
          const lineStart = sourceText.lastIndexOf('\n', i) + 1;
          column = i - lineStart + 1;
          break;
        }
      }
    }
    return { pos: commaPos, column };
  }

  private checkTrailingComma(
    node: ts.ArrayLiteralExpression | ts.ObjectLiteralExpression,
    arkFile: ArkFile,
    type: 'arrays' | 'objects'
  ): void {
    const elements = ts.isArrayLiteralExpression(node)
      ? node.elements
      : node.properties;
    
    if (elements.length === 0) return;

    const lastElement = elements[elements.length - 1];
    const sourceFile = lastElement.getSourceFile();
    const nodeText = node.getText();
    
    const hasTrailingComma = type === 'arrays' ? /,\s*\]/.test(nodeText) : /,\s*}/.test(nodeText);
    
    const lastElementPos = lastElement.getEnd();
    const closeBracketPos = node.getEnd();
    const lastElementLine = sourceFile.getLineAndCharacterOfPosition(lastElementPos).line;
    const closeBracketLine = sourceFile.getLineAndCharacterOfPosition(closeBracketPos).line;
    
    const isMultiline = closeBracketLine > lastElementLine;

    const context = {
      hasTrailingComma: hasTrailingComma,
      isMultiline: isMultiline
    };
    
    const violation = this.checkTrailingCommaViolation(this.getRuleOption(type), context);

    if (violation.hasViolation) {
      const pos = lastElement.getEnd();
      const { line, character } = 
        sourceFile.getLineAndCharacterOfPosition(pos);

      const defect = new Defects(
        line + 1,
        character + 1,
        character + 2,
        violation.message,
        this.metaData.severity,
        this.rule.ruleId,
        arkFile.getFilePath(),
        this.metaData.ruleDocPath,
        true,
        false,
        false
      );

      let fix: RuleFix;
      if (hasTrailingComma) {
        const commaInfo = this.findTrailingCommaPos(lastElement, node);
        if (commaInfo.pos !== -1) {
          fix = { range: [commaInfo.pos, commaInfo.pos + 1], text: '' };
        } else {
          fix = { range: [lastElement.getEnd(), lastElement.getEnd()], text: '' };
        }
      } else {
        fix = { range: [lastElement.getEnd(), lastElement.getEnd()], text: ',' };
      }

      this.issues.push(new IssueReport(defect, fix));
      RuleListUtil.push(defect);
    }
  }

  private checkEnumTrailingComma(
    node: ts.EnumDeclaration,
    arkFile: ArkFile,
    option: string
  ): void {
    if (node.members.length === 0) return;
    
    const lastMember = node.members[node.members.length - 1];
    const sourceFile = lastMember.getSourceFile();
    const enumText = node.getText();
    
    const enumBodyMatch = enumText.match(/{([^}]*)}/);
    if (!enumBodyMatch) return;
    
    const enumBody = enumBodyMatch[1].trim();
    const hasTrailingComma = /,\s*$/.test(enumBody);
    
    const openBracketPos = enumText.indexOf('{');
    const closeBracketPos = enumText.lastIndexOf('}');
    if (openBracketPos === -1 || closeBracketPos === -1) return;
    
    const fullStart = node.getStart();
    const openBracketLine = sourceFile.getLineAndCharacterOfPosition(fullStart + openBracketPos).line;
    const closeBracketLine = sourceFile.getLineAndCharacterOfPosition(fullStart + closeBracketPos).line;
    
    const isMultiline = closeBracketLine !== openBracketLine;

    const context = {
      hasTrailingComma: hasTrailingComma,
      isMultiline: isMultiline
    };
    
    const violation = this.checkTrailingCommaViolation(option, context);

    if (violation.hasViolation) {
      const enumStart = node.getStart();
      const { line, character } = sourceFile.getLineAndCharacterOfPosition(enumStart);
      
      const enumKeywordPos = node.getStart();
      const enumKeywordCol = sourceFile.getLineAndCharacterOfPosition(enumKeywordPos).character + 1;

      const defect = new Defects(
        line + 1,
        enumKeywordCol,
        enumKeywordCol + 1,
        violation.message,
        this.metaData.severity,
        this.rule.ruleId,
        arkFile.getFilePath(),
        this.metaData.ruleDocPath,
        true,
        false,
        false
      );

      let fix: RuleFix;
      if (hasTrailingComma) {
        const commaInfo = this.findTrailingCommaPos(lastMember, node);
        if (commaInfo.pos !== -1) {
          fix = { range: [commaInfo.pos, commaInfo.pos + 1], text: '' };
        } else {
          fix = { range: [lastMember.getEnd(), lastMember.getEnd()], text: '' };
        }
      } else {
        fix = { range: [lastMember.getEnd(), lastMember.getEnd()], text: ',' };
      }

      this.issues.push(new IssueReport(defect, fix));
      RuleListUtil.push(defect);
    }
  }

  private checkGenericTrailingComma(
    typeParameters: ts.NodeArray<ts.TypeParameterDeclaration>,
    arkFile: ArkFile,
    option: string
  ): void {
    if (!this.shouldCheckGeneric(typeParameters)) {
      return;
    }
  
    const context = this.getGenericContext(typeParameters);
    const violation = this.checkGenericViolation(context, option);
  
    if (violation.hasViolation) {
      this.reportGenericViolation(context, violation.message, arkFile);
    }
  }
  
  private shouldCheckGeneric(typeParameters: ts.NodeArray<ts.TypeParameterDeclaration>): boolean {
    return typeParameters.length > 0;
  }
  
  private getGenericContext(typeParameters: ts.NodeArray<ts.TypeParameterDeclaration>): {
    lastParam: ts.TypeParameterDeclaration,
    sourceFile: ts.SourceFile,
    parent: ts.Node,
    parentText: string,
    genericText: string,
    hasTrailingComma: boolean,
    isMultiline: boolean,
    startBracket: number,
    endBracket: number
  } {
    const lastParam = typeParameters[typeParameters.length - 1];
    const sourceFile = lastParam.getSourceFile();
    const parent = typeParameters[0].parent;
    const parentText = parent.getText();
    
    const startBracket = parentText.indexOf('<');
    const endBracket = parentText.indexOf('>', startBracket);
    const genericText = parentText.slice(startBracket + 1, endBracket);
    
    const hasTrailingComma = /,\s*$/.test(genericText.trim());
    const isMultiline = genericText.includes('\n');
  
    return {
      lastParam,
      sourceFile,
      parent,
      parentText,
      genericText,
      hasTrailingComma,
      isMultiline,
      startBracket,
      endBracket
    };
  }
  
  private checkGenericViolation(
    context: ReturnType<typeof this.getGenericContext>,
    option: string
  ): { hasViolation: boolean, message: string } {
    return this.checkTrailingCommaViolation(option, {
      hasTrailingComma: context.hasTrailingComma,
      isMultiline: context.isMultiline
    });
  }
  
  private reportGenericViolation(
    context: ReturnType<typeof this.getGenericContext>,
    message: string,
    arkFile: ArkFile
  ): void {
    const { reportLine, reportColumn } = this.getReportPosition(context);
  
    const defect = this.createGenericDefect(
      reportLine,
      reportColumn,
      message,
      arkFile
    );
  
    const fix = this.createGenericFix(context);
  
    this.issues.push(new IssueReport(defect, fix));
    RuleListUtil.push(defect);
  }
  
  private getReportPosition(context: ReturnType<typeof this.getGenericContext>): {
    reportLine: number,
    reportColumn: number
  } {
    const isFunctionDeclaration = ts.isFunctionDeclaration(context.parent);
    
    if (isFunctionDeclaration) {
      const functionStart = context.parent.getStart();
      const genericStart = functionStart + context.startBracket;
      const { line, character } = context.sourceFile.getLineAndCharacterOfPosition(genericStart);
      return {
        reportLine: line + 1,
        reportColumn: character + 1
      };
    } else {
      const elementEnd = context.lastParam.getEnd();
      const { line, character } = context.sourceFile.getLineAndCharacterOfPosition(elementEnd);
      return {
        reportLine: line + 1,
        reportColumn: character + 1
      };
    }
  }
  
  private createGenericDefect(
    line: number,
    column: number,
    message: string,
    arkFile: ArkFile
  ): Defects {
    return new Defects(
      line,
      column,
      column + 1,
      message,
      this.metaData.severity,
      this.rule.ruleId,
      arkFile.getFilePath(),
      this.metaData.ruleDocPath,
      true,
      false,
      false
    );
  }
  
  private createGenericFix(context: ReturnType<typeof this.getGenericContext>): RuleFix {
    if (context.hasTrailingComma) {
      const commaInfo = this.findTrailingCommaPos(context.lastParam, context.parent);
      if (commaInfo.pos !== -1) {
        return { range: [commaInfo.pos, commaInfo.pos + 1], text: '' };
      }
      return { range: [context.lastParam.getEnd(), context.lastParam.getEnd()], text: '' };
    }
    return { range: [context.lastParam.getEnd(), context.lastParam.getEnd()], text: ',' };
  }

  private checkTupleTrailingComma(
    node: ts.TupleTypeNode,
    arkFile: ArkFile,
    option: string
  ): void {
    if (!this.shouldCheckTuple(node)) {
      return;
    }
  
    const tupleContext = this.getTupleContext(node);
    const violation = this.checkTupleViolation(tupleContext, option);
    
    if (violation.hasViolation) {
      this.reportTupleViolation(node, tupleContext, violation.message, arkFile);
    }
  }
  
  private shouldCheckTuple(node: ts.TupleTypeNode): boolean {
    return node.elements.length > 0;
  }
  
  private getTupleContext(node: ts.TupleTypeNode): {
    lastElement: ts.TypeNode,
    tupleText: string,
    hasTrailingComma: boolean,
    isMultiline: boolean
  } {
    const lastElement = node.elements[node.elements.length - 1];
    const tupleText = node.getText();
    const hasTrailingComma = /,\s*\]/.test(tupleText);
    const isMultiline = this.isMultiline(node);
  
    return {
      lastElement,
      tupleText,
      hasTrailingComma,
      isMultiline
    };
  }
  
  private checkTupleViolation(
    context: ReturnType<typeof this.getTupleContext>,
    option: string
  ): { hasViolation: boolean, message: string } {
    return this.checkTrailingCommaViolation(option, {
      hasTrailingComma: context.hasTrailingComma,
      isMultiline: context.isMultiline
    });
  }
  
  private reportTupleViolation(
    node: ts.TupleTypeNode,
    context: ReturnType<typeof this.getTupleContext>,
    message: string,
    arkFile: ArkFile
  ): void {
    const sourceFile = context.lastElement.getSourceFile();
    const tupleStart = node.getStart();
    const { line, character } = sourceFile.getLineAndCharacterOfPosition(tupleStart);
    
    const bracketPos = node.getStart();
    const bracketCol = sourceFile.getLineAndCharacterOfPosition(bracketPos).character + 1;
  
    const defect = this.createTupleDefect(
      line + 1,
      bracketCol,
      message,
      arkFile
    );
  
    const fix = this.createTupleFix(context, node);
  
    this.issues.push(new IssueReport(defect, fix));
    RuleListUtil.push(defect);
  }
  
  private createTupleDefect(
    line: number,
    column: number,
    message: string,
    arkFile: ArkFile
  ): Defects {
    return new Defects(
      line,
      column,
      column + 1,
      message,
      this.metaData.severity,
      this.rule.ruleId,
      arkFile.getFilePath(),
      this.metaData.ruleDocPath,
      true,
      false,
      false
    );
  }
  
  private createTupleFix(
    context: ReturnType<typeof this.getTupleContext>,
    node: ts.TupleTypeNode
  ): RuleFix {
    if (context.hasTrailingComma) {
      const commaInfo = this.findTrailingCommaPos(context.lastElement, node);
      if (commaInfo.pos !== -1) {
        return { range: [commaInfo.pos, commaInfo.pos + 1], text: '' };
      }
      return { range: [context.lastElement.getEnd(), context.lastElement.getEnd()], text: '' };
    }
    return { range: [context.lastElement.getEnd(), context.lastElement.getEnd()], text: ',' };
  }

  private hasTrailingComma(node: ts.Node, lastElement: ts.Node): boolean {
    const sourceText = node.getSourceFile().getFullText();
    const textBetween = sourceText.substring(lastElement.getEnd(), node.getEnd());
    return /,\s*[}>)\]]/.test(textBetween);
  }

  private isMultiline(node: ts.Node): boolean {
    const sourceFile = node.getSourceFile();
    let startPos: number;
    let endPos: number;
    let openBracketPos: number;
    let closeBracketPos: number;

    if (ts.isArrayLiteralExpression(node)) {
      if (node.elements.length === 0) return false;
      startPos = node.elements[0].getStart();
      endPos = node.elements[node.elements.length - 1].getEnd();
      openBracketPos = node.getStart();
      closeBracketPos = node.getEnd();
    } else if (ts.isObjectLiteralExpression(node)) {
      if (node.properties.length === 0) return false;
      startPos = node.properties[0].getStart();
      endPos = node.properties[node.properties.length - 1].getEnd();
      openBracketPos = node.getStart();
      closeBracketPos = node.getEnd();
    } else if (ts.isEnumDeclaration(node)) {
      if (node.members.length === 0) return false;
      startPos = node.members[0].getStart();
      endPos = node.members[node.members.length - 1].getEnd();
      openBracketPos = node.members.pos;
      closeBracketPos = node.members.end;
    } else if (ts.isTupleTypeNode(node)) {
      if (node.elements.length === 0) return false;
      startPos = node.elements[0].getStart();
      endPos = node.elements[node.elements.length - 1].getEnd();
      openBracketPos = node.getStart();
      closeBracketPos = node.getEnd();
    } else {
      return false;
    }

    const startLine = sourceFile.getLineAndCharacterOfPosition(startPos).line;
    const endLine = sourceFile.getLineAndCharacterOfPosition(endPos).line;
    const openBracketLine = sourceFile.getLineAndCharacterOfPosition(openBracketPos).line;
    const closeBracketLine = sourceFile.getLineAndCharacterOfPosition(closeBracketPos).line;

    return closeBracketLine !== openBracketLine || endLine !== startLine || startLine !== openBracketLine;
  }

  private checkImportExportTrailingComma(
    node: ts.ImportDeclaration | ts.ExportDeclaration,
    arkFile: ArkFile,
    option: string
  ): void {
    let elements: ts.NodeArray<ts.ImportSpecifier | ts.ExportSpecifier> | undefined;
    
    if (ts.isImportDeclaration(node)) {
      elements = node.importClause?.namedBindings && ts.isNamedImports(node.importClause.namedBindings) 
        ? node.importClause.namedBindings.elements 
        : undefined;
    } else {
      elements = node.exportClause && ts.isNamedExports(node.exportClause) 
        ? node.exportClause.elements 
        : undefined;
    }

    if (!elements || elements.length === 0) return;

    const lastElement = elements[elements.length - 1];
    const hasTrailingComma = this.hasTrailingComma(node as any, lastElement);
    const isMultiline = this.isMultiline(node);

    this.checkAndReportViolation(option, hasTrailingComma, isMultiline, lastElement, arkFile);
  }

  private hasFunctionTrailingComma(
    node: ts.CallExpression | ts.FunctionDeclaration | ts.NewExpression,
    lastElement: ts.Node
  ): boolean {
    const sourceText = node.getSourceFile().getFullText();
    const textBetween = sourceText.substring(lastElement.getEnd(), node.getEnd());
    return /,\s*[)]/.test(textBetween);
  }

  private checkFunctionTrailingComma(
    node: ts.CallExpression | ts.FunctionDeclaration | ts.NewExpression,
    arkFile: ArkFile,
    option: string
  ): void {
    if (ts.isCallExpression(node)) {
      this.checkCallExpressionComma(node, arkFile, option);
      return;
    }

    if (ts.isNewExpression(node)) {
      this.checkNewExpressionComma(node, arkFile, option);
      return;
    }

    if (ts.isFunctionDeclaration(node)) {
      this.checkFunctionDeclarationComma(node, arkFile, option);
      return;
    }
  }

  private checkCallExpressionComma(
    node: ts.CallExpression,
    arkFile: ArkFile,
    option: string
  ): void {
    const args = node.arguments;
    if (args.length === 0) return;

    const lastArg = args[args.length - 1];
    
    if (ts.isObjectLiteralExpression(lastArg)) {
      this.checkTrailingComma(lastArg, arkFile, 'objects');
      return;
    }
    
    const hasTrailingComma = this.hasFunctionTrailingComma(node, lastArg);
    const isMultiline = this.isMultiline(node);
    
    this.checkAndReportViolation(option, hasTrailingComma, isMultiline, lastArg, arkFile);
  }

  private checkNewExpressionComma(
    node: ts.NewExpression,
    arkFile: ArkFile,
    option: string
  ): void {
    const args = node.arguments;
    if (!args || args.length === 0) return;

    const lastArg = args[args.length - 1];
    const hasTrailingComma = this.hasFunctionTrailingComma(node, lastArg);
    const isMultiline = this.isMultiline(node);
    
    this.checkAndReportViolation(option, hasTrailingComma, isMultiline, lastArg, arkFile);
  }

  private checkFunctionDeclarationComma(
    node: ts.FunctionDeclaration,
    arkFile: ArkFile,
    option: string
  ): void {
    const functionText = node.getText();
    const paramMatch = functionText.match(/\(([^)]*)\)/);
    
    if (paramMatch && paramMatch[1].trim()) {
      const paramText = paramMatch[1].trim();
      const lastParamMatch = paramText.match(/,\s*$/);
      
      const parameters = node.parameters;
      if (parameters.length > 0) {
        const lastParam = parameters[parameters.length - 1];
        const hasTrailingComma = lastParamMatch !== null;
        const isMultiline = paramText.includes('\n');
        
        this.checkAndReportViolation(option, hasTrailingComma, isMultiline, lastParam, arkFile);
      }
    }
  }

  private checkAndReportViolation(
    option: string,
    hasTrailingComma: boolean,
    isMultiline: boolean,
    lastElement: ts.Node,
    arkFile: ArkFile
  ): void {
    const violation = this.checkViolation(option, hasTrailingComma, isMultiline);
    
    if (violation.hasViolation) {
      this.reportViolation(violation.message, lastElement, hasTrailingComma, arkFile);
    }
  }
  
  private checkViolation(
    option: string,
    hasTrailingComma: boolean,
    isMultiline: boolean
  ): { hasViolation: boolean; message: string } {
    const message = 'never';
    const violationCheckers: Record<string, () => { hasViolation: boolean; message: string }> = {
      'never': () => this.checkNeverOption(hasTrailingComma),
      'always': () => this.checkAlwaysOption(hasTrailingComma),
      'always-multiline': () => this.checkAlwaysMultilineOption(hasTrailingComma, isMultiline),
      'only-multiline': () => this.checkOnlyMultilineOption(hasTrailingComma, isMultiline)
    };
  
    const checker = violationCheckers[option] || violationCheckers[message];
    return checker();
  }
  
  private checkNeverOption(hasTrailingComma: boolean): { hasViolation: boolean; message: string } {
    return {
      hasViolation: hasTrailingComma,
      message: 'Unexpected trailing comma.'
    };
  }
  
  private checkAlwaysOption(hasTrailingComma: boolean): { hasViolation: boolean; message: string } {
    return {
      hasViolation: !hasTrailingComma,
      message: 'Missing trailing comma.'
    };
  }
  
  private checkAlwaysMultilineOption(
    hasTrailingComma: boolean,
    isMultiline: boolean
  ): { hasViolation: boolean; message: string } {
    if (isMultiline && !hasTrailingComma) {
      return {
        hasViolation: true,
        message: 'Missing trailing comma.'
      };
    }
    if (!isMultiline && hasTrailingComma) {
      return {
        hasViolation: true,
        message: 'Unexpected trailing comma.'
      };
    }
    return {
      hasViolation: false,
      message: ''
    };
  }
  
  private checkOnlyMultilineOption(
    hasTrailingComma: boolean,
    isMultiline: boolean
  ): { hasViolation: boolean; message: string } {
    return {
      hasViolation: !isMultiline && hasTrailingComma,
      message: 'Unexpected trailing comma.'
    };
  }
  
  private reportViolation(
    message: string,
    lastElement: ts.Node,
    hasTrailingComma: boolean,
    arkFile: ArkFile
  ): void {
    const { line, column } = this.getViolationPosition(lastElement);
    const defect = this.createViolationDefect(line, column, message, arkFile);
    const fix = this.createViolationFix(lastElement, hasTrailingComma);
    this.issues.push(new IssueReport(defect, fix));
    RuleListUtil.push(defect);
  }
  
  private getViolationPosition(lastElement: ts.Node): { line: number; column: number } {
    const sourceFile = lastElement.getSourceFile();
    const pos = lastElement.getEnd();
    const { line, character } = sourceFile.getLineAndCharacterOfPosition(pos);
  
    return {
      line: line + 1,
      column: character + 1
    };
  }
  
  private createViolationDefect(
    line: number,
    column: number,
    message: string,
    arkFile: ArkFile
  ): Defects {
    return new Defects(
      line,
      column,
      column + 1,
      message,
      this.metaData.severity,
      this.rule.ruleId,
      arkFile.getFilePath(),
      this.metaData.ruleDocPath,
      true,
      false,
      true
    );
  }
  
  private createViolationFix(lastElement: ts.Node, hasTrailingComma: boolean): RuleFix {
    if (hasTrailingComma) {
      const commaInfo = this.findTrailingCommaPos(lastElement, lastElement.parent);
      if (commaInfo.pos !== -1) {
        return { range: [commaInfo.pos, commaInfo.pos + 1], text: '' };
      }
      return { range: [lastElement.getEnd(), lastElement.getEnd()], text: '' };
    }
    return { range: [lastElement.getEnd(), lastElement.getEnd()], text: ',' };
  }

  private checkTrailingCommaViolation(
    option: string,
    context: {
      hasTrailingComma: boolean;
      isMultiline: boolean;
    }
  ): { hasViolation: boolean; message: string } {
    let hasViolation = false;
    let message = '';

    switch (option) {
      case 'never':
        if (context.hasTrailingComma) {
          hasViolation = true;
          message = 'Unexpected trailing comma.';
        }
        break;
      case 'always':
        if (!context.hasTrailingComma) {
          hasViolation = true;
          message = 'Missing trailing comma.';
        }
        break;
      case 'always-multiline':
        if (context.isMultiline && !context.hasTrailingComma) {
          hasViolation = true;
          message = 'Missing trailing comma.';
        } else if (!context.isMultiline && context.hasTrailingComma) {
          hasViolation = true;
          message = 'Unexpected trailing comma.';
        }
        break;
      case 'only-multiline':
        if (!context.isMultiline && context.hasTrailingComma) {
          hasViolation = true;
          message = 'Unexpected trailing comma.';
        }
        break;
    }

    return { hasViolation, message };
  }
}